/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <esent.h>
#include <memory>
#include <functional>
#include <baja/db/db_interfaces.h>

 namespace baja { namespace storage {

namespace b = baja;

typedef std::function<void(std::shared_ptr<itable>,
                           std::shared_ptr<std::vector<std::shared_ptr<icell>>>,
                           std::shared_ptr<iresult>) > for_each_func;

class db_jet_engine : public iengine
{
public:
    db_jet_engine();
    ~db_jet_engine();
    static std::shared_ptr<iengine> create();

private:
    struct strings_buffer
    {
        std::vector<std::wstring*> strings;
        ~strings_buffer()
        {
            std::vector<std::wstring*>::iterator it;
            for (it = strings.begin() ; it < strings.end() ; ++it)
            {
                delete *it;
            }
        }
    };

    struct string_buffer
    {
        b::uint32 byteCount;
        b::uint32 charCount;
        wchar_t* buffer;

        string_buffer(b::uint32 byteCount_) :
            byteCount(byteCount_),
            buffer(nullptr)
        {
            charCount = byteCount / sizeof(wchar_t);
            buffer = new wchar_t[charCount];
            buffer[charCount - 1] = L'\0';
        }
        void rightTrimSpaces()
        {
            if (charCount < 2)
            {
                return;
            }
            buffer[charCount - 1] = L'\0';
            b::uint32 lastChar = charCount - 2;
            while (lastChar >= 0 && buffer[lastChar] == L' ')
            {
                buffer[lastChar] = L'\0';
                if (lastChar == 0)
                {
                    break;
                }
                --lastChar;
            }
        }
        ~string_buffer()
        {
            delete [] buffer;
        }
    };

    enum operation_type
    {
        insertRecord,
        updateRecord
    };

    enum selection_type
    {
        select_equal,
        select_equal_or_great_than
    };

    enum key_type
    {
        start_key,
        end_key
    };

    // iengine
    void createInstance(std::wstring workingFolderName);

    void beginSession();

    void createDatabase(std::wstring dbFileName);
    void openDatabase(std::wstring fileName);

    void setPageSize(unsigned int size);
    void setTempPath(std::wstring tempPath);
    void setLogFilePath(std::wstring logFilePath);
    void setSystemPath(std::wstring systemPath);

    void createTable(std::shared_ptr<itable> tableDefinition);
    void openTable(std::shared_ptr<itable> tableDefinition);

    void insert(std::shared_ptr<itable> table,
                std::shared_ptr<std::vector<std::shared_ptr<icell>>> row);

    void update(std::shared_ptr<itable> table,
                std::shared_ptr<iindex> index,
                std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                b::uint32 criteriaCount,
                bool assumeSingleMatch);

    void read(std::shared_ptr<itable> table,
              std::shared_ptr<iindex> index,
              std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
              b::uint32 criteriaCount,
              bool assumeSingleMatch,
              std::shared_ptr<iresult> result);

    void readAll(std::shared_ptr<itable> table,
                 std::shared_ptr<iindex> index,
                 std::shared_ptr<iresult> result);

    void dispose(std::shared_ptr<itable> table,
                std::shared_ptr<iindex> index,
                std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                b::uint32 criteriaCount,
                bool assumeSingleMatch);

    void disposeAll(
        std::shared_ptr<itable> table,
        std::shared_ptr<iindex> index);

private:
    // As long as every session is a context in which all operations with databases are performed, 
    // every session controls transaction borders. A transaction may start and finish only within 
    // the limits of a session. An application may create more than one session to a database, possibly 
    // increasing its performance thereby.

    JET_INSTANCE _instance;
    JET_SESID _sessionId;
    JET_DBID _dbID;

    for_each_func _updateRowBinding;
    for_each_func _readRowBinding;
    for_each_func _deleteRowBinding;

    std::recursive_mutex _mutex;

    void shutdown();

    void writeRow(std::shared_ptr<itable> table,
                  std::shared_ptr<std::vector<std::shared_ptr<icell>>> inputRow,
                  operation_type operationType);

    void updateRow(std::shared_ptr<itable> table,
                   std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                   std::shared_ptr<iresult> dummyResult);

    void readRow(std::shared_ptr<itable> table,
                 std::shared_ptr<std::vector<std::shared_ptr<icell>>> dummyRow,
                 std::shared_ptr<iresult> result);

    void deleteRow(std::shared_ptr<itable> table,
                   std::shared_ptr<std::vector<std::shared_ptr<icell>>> dummyRow,
                   std::shared_ptr<iresult> dummResult);

    void makeKey(std::shared_ptr<itable> table,
                              std::shared_ptr<iindex> index,
                              std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                              b::uint32 criteriaCount,
                              key_type keyType);

    bool setStartKey(std::shared_ptr<itable> table,
                              std::shared_ptr<iindex> index,
                              std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                              b::uint32 criteriaCount,
                              selection_type selectionType);

    bool setEndKey(std::shared_ptr<itable> table,
                              std::shared_ptr<iindex> index,
                              std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                              b::uint32 criteriaCount);

    void forEachMatch(std::shared_ptr<itable> table,
                      std::shared_ptr<iindex> index,
                      std::shared_ptr<std::vector<std::shared_ptr<icell>>> criteriaRow,
                      b::uint32 criteriaCount,
                      bool assumeSingleMatch,
                      for_each_func func,
                      std::shared_ptr<iresult> result);

    void forAll(std::shared_ptr<itable> table,
                std::shared_ptr<iindex> index,
                for_each_func func,
                std::shared_ptr<iresult> result);
};

std::shared_ptr<iengine> createEngine();

}} // namespace s